﻿#define RadOfCircle
using System;
using System.Timers;
using Model01.Management;
using Model01.Objects;

namespace Model01.Transportation
{
    /// <summary>
    /// class of the car
    /// </summary>
    public abstract class Car : Objects.Object
    {
        #region Fields

        protected double maxSpeed = 0;
        protected double acceleration = 0.0;
        protected double deceleration = 0.0;
        protected double minDeceleration = 0.0;
        protected double maxDeceleration = 0.0;
        protected double currentAngle = 0.0;
        private Point[] origCorners;
        private Point leftHighCorner;        
        private Point leftLowCorner;
        private Point rightHighCorner;        
        private Point rightLowCorner;
        private int radOfCircle;
        protected Timer timer = new Timer((int)Manager.TimeRepaint);

        #endregion        

        /// <summary>
        ///
        /// </summary>
        /// <param name="x">X coordinate of the car center.</param>
        /// <param name="y">Y coordinate of the car center.</param>
        /// <param name="size">Size of the car</param>
        public Car(int x, int y, Size size) : base(x, y, size) 
        {
            timer.Elapsed += new ElapsedEventHandler(OnCarStep);
            SetCorners();
        }

        #region Methods

        protected virtual void OnCarStep(object sender, ElapsedEventArgs e) { }

        /// <summary>
        /// Rotates point on given angle.
        /// </summary>
        /// <param name="point">Point that must be rotate.</param>
        /// <param name="angle">Angle of rotating in rad.</param>
        private Point RotatePoint(Point point)
        {
            double x = Math.Round(((point.X) * Math.Cos(currentAngle) - (point.Y) * Math.Sin(currentAngle)));
            double y = Math.Round(((point.X) * Math.Sin(currentAngle) + (point.Y) * Math.Cos(currentAngle)));
            return new Point((int)x, (int)y);
        }

        /// <summary>
        /// Rotates four corners of a car on given angle.
        /// </summary>
        /// <param name="angle">Angle of rotating in deg.</param>
        protected void Rotate()
        {
            leftHighCorner = RotatePoint(origCorners[0]);
            leftLowCorner = RotatePoint(origCorners[1]);
            rightHighCorner = RotatePoint(origCorners[2]);
            rightLowCorner = RotatePoint(origCorners[3]);            
            OnRotate(this, new OnRotateEventArgs(currentAngle));
        }
        
        /// <summary>
        /// Sets initual value of four corners points of a car.
        /// </summary>
        private void SetCorners()
        {
            radOfCircle = (int)Math.Sqrt(((double)(Size.Width * Size.Width) / 4) + (double)((Size.Height * Size.Height) / 4));
            leftHighCorner = new Point(- Size.Width / 2, - Size.Height / 2);
            leftLowCorner = new Point(- Size.Width / 2, Size.Height / 2);
            rightHighCorner = new Point(Size.Width / 2, - Size.Height / 2);
            rightLowCorner = new Point(Size.Width / 2, Size.Height / 2);
            origCorners = new Point[] { leftHighCorner, leftLowCorner, rightHighCorner, rightLowCorner };
        }
        
        /// <summary>
        /// Makes a car go to its route.
        /// </summary>
        public abstract void Go();

        /// <summary>
        /// Makes a car go to it's destination point.
        /// </summary>
        /// <param name="destination">Model01.Objects.Point where does the car go.</param>
        protected abstract void GoTo(Point destination);

        #endregion

        #region Events

        public event OnRotateEvent OnRotate = delegate { };

        #endregion        

        #region Properties

        /// <summary>
        /// Gets or sets current angle of track of a car in rad. Angle of track - angle between axis OX and current track.
        /// </summary>
        public double CurrentAngle
        {
            get { return currentAngle; }
            set 
            {
                currentAngle = value;
                Rotate();
            }
        }

        /// <summary>
        /// Gets coocrdinate of left high corner of a car.
        /// </summary>
        public Point LeftHighCorner
        {
            get { return new Point(X + radOfCircle + leftHighCorner.X, Y + radOfCircle + leftHighCorner.Y); }
        }

        /// <summary>
        /// Gets coocrdinate of left low corner of a car.
        /// </summary>
        public Point LeftLowCorner
        {
            get { return new Point(X + radOfCircle + leftLowCorner.X, Y + radOfCircle + leftLowCorner.Y); }
        }

        /// <summary>
        /// Gets coocrdinate of right high corner of a car.
        /// </summary>
        public Point RightHighCorner
        {
            get { return new Point(X + radOfCircle + rightHighCorner.X, Y + radOfCircle + rightHighCorner.Y); }
        }

        /// <summary>
        /// Gets coocrdinate of right low corner of a car.
        /// </summary>
        public Point RightLowCorner
        {
            get { return new Point(X + radOfCircle + rightLowCorner.X, Y + radOfCircle + rightLowCorner.Y); }
        }

        /// <summary>
        /// Gets a value of the radius of the circle around the car
        /// </summary>
        public int RadiusOfCircle
        {
            get { return radOfCircle; }
        }

        /// <summary>
        /// Gets or sets max speed of a car in km per hour.
        /// </summary>
        public double MaxSpeed
        {
            get { return maxSpeed; }
            set { maxSpeed = Manager.ModelSpeed(value); }
        }

        /// <summary>
        /// Gets or sets acceleration of a car in meter per second.
        /// </summary>
        public double Acceleration
        {
            get { return acceleration; }
            set { acceleration = Manager.ModelAcseleration(value); }
        }

        /// <summary>
        /// Gets or sets deceleration of a car in meter per second.
        /// </summary>
        public double Deceleration
        {
            get { return deceleration; }
            set 
            {
                deceleration = Manager.ModelAcseleration(value);
                minDeceleration = deceleration / 2.0;
                maxDeceleration = 2 * deceleration;
            }
        }

        #endregion
    }

    #region Description of event delegates and args

    public delegate void OnRotateEvent(object sender, OnRotateEventArgs e);

    public class OnRotateEventArgs : EventArgs
    {
        private double angle;

        public OnRotateEventArgs(double angle)
        {
            Angle = angle;
        }

        public double Angle
        {
            get { return angle; }
            set { angle = value; }
        }
    }

    #endregion
}
